#!/usr/bin/python
# -*- coding: UTF-8 -*-
import sys, os
import openpyxl
import matplotlib.pyplot as plt
import math
from scipy.interpolate import make_interp_spline
import numpy as np
from sympy import symbols, Eq, solve
from itertools import combinations, permutations
import sympy

class NTCTable(object):
    """docstring for NTCTable"""
    def __init__(self):
        super(NTCTable, self).__init__()
        self.filename = ""
        self.workbook = ""
        self.sheet = ""
        self.calibration_x = []
        self.calibration_y = []
        self.raw_x = []
        self.raw_y = []
        # self.raw_x = [-5, 0, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50]
        # self.raw_y = [853, 665, 560, 480, 404, 342, 300, 253, 210, 180, 156, 136]
        # self.raw_x = [-40, -30, -20, -10, 0, 10, 20, 25, 30, 40, 50, 60]
        # self.raw_y = [201.7, 116.9, 70.03, 43.35, 27.62, 18.07, 12.11, 10, 8.302, 5.810, 4.415, 3.011]
        self.A = 0
        self.B = 0
        self.C = 0
        # self.A = 0.00266614728710135
        # self.B = 0.000296430199128152
        # self.C = 4.11589646091922e-7
        self.A = 0.00155477640601503
        self.B = 0.000296403964738230
        self.C = 6.18796285197252e-7

    def loadExcelFile(self, excel_file_name):
        if excel_file_name != None and excel_file_name != "":
            try:
                self.workbook = openpyxl.load_workbook(excel_file_name)
                self.sheet = self.workbook["temp_tab"]
                if self.sheet['A1'].value == "temp" and self.sheet["B1"].value == "adc":
                    print("Load excel file " + excel_file_name + " success")
                    self.filename = excel_file_name
                    return True
                else:
                    self.workbook = ""
                    self.sheet = ""
                    print("Excel file not standard")
                    return False
            except Exception as e:
                print("Load excel crashed: " + str(e))
                return False
        else:
            print("Excel file name illegal!!")
            return False

    def savaCalibrationDataToExcelFile(self):
        if self.workbook != "" and self.sheet != "" and self.filename != "":
            self.sheet["C1"].value = "extend_temp"
            self.sheet["D1"].value = "calibrated_adc"
            self.sheet["E1"].value = "int_calibrated_adc"
            for i in range(len(self.calibration_x)):
                self.sheet.cell(row=i+2, column=3).value = self.calibration_x[i]
                self.sheet.cell(row=i+2, column=4).value = self.calibration_y[i]
                self.sheet.cell(row=i+2, column=5).value = (int)(self.calibration_y[i])
            try:
                self.workbook.save(self.filename)
            except Exception as e:
                print("Problem happened, maybe you need to close your excel file named: " + str(self.filename))

    def loadRawTable(self):
        if self.workbook != "" and self.sheet != "":
            self.raw_x.clear()
            self.raw_y.clear()
            for i in range(2, self.sheet.max_row):
                A_part = self.sheet.cell(row=i, column=1).value
                B_part = self.sheet.cell(row=i, column=2).value
                if B_part != None:
                    if A_part != None:
                        self.raw_x.append(A_part)
                        self.raw_y.append(B_part)
            print(self.raw_x)
            print(self.raw_y)
            # plt.plot(self.raw_x, self.raw_y)
            # plt.title("Raw table")
            # plt.xlabel("Temperatue")
            # plt.ylabel("Resistor")
            # # Start with raw_x[0] and step is 5
            # plt.xticks([x for x in range(self.raw_x[0], max(self.raw_x) + 1) if x % 5 == 0])
            # # plt.show()

    # def moving_average(self, interval, windowsize):
    #     window = np.ones(int(windowsize)) / float(windowsize)
    #     re = np.convolve(interval, window, 'same')
    #     return re

    def getFahrenheitFromCelsius(self, C):
        return (C + 273.15)

    # Cx for celsius_x, Rx for resistor_x
    def getNTCParaBeta(self, C1, C2, R1, R2):
        k1 = self.getFahrenheitFromCelsius(C1)
        k2 = self.getFahrenheitFromCelsius(C2)
        b = ((k1 * k2) / (k2 - k1)) * math.log(R1 / R2)
        return b

    # c for celsius, r for resistor
    def getEquation(self, c, r):
        A, B, C = symbols('A,B,C')
        eq = Eq((A + B * math.log(r) + C * math.pow(math.log(r), 3)), (1 / self.getFahrenheitFromCelsius(c)))
        return eq

    # c for celsius, r for resistor
    def getSteinhartHartEquation(self, c1, c2, c3, r1, r2, r3):
        A, B, C = symbols('A,B,C')
        eq1 = self.getEquation(c1, r1)
        eq2 = self.getEquation(c2, r2)
        eq3 = self.getEquation(c3, r3)
        rslt = (solve((eq1, eq2, eq3), (A, B, C)))
        # eq_rslt[A], eq_rslt[B], eq_rslt[C]
        # return eq_rslt
        eq_rlst = [rslt[A], rslt[B], rslt[C]]
        return eq_rlst

    def getSteinhartHartEquationList(self, index_list, temp_list, resistor_list):
        eq_list = []
        index_list_len = len(index_list)
        for l in index_list:
            eq = self.getSteinhartHartEquation(temp_list[l[0]], temp_list[l[1]], temp_list[l[2]], 
                resistor_list[l[0]], resistor_list[l[1]], resistor_list[l[2]])
            print(eq)
            print(index_list_len)
            index_list_len -= 1
            eq_list.append(eq)
        return eq_list

    def analyzeEquationList(self, eq_list):
        self.A = 0
        self.B = 0
        self.C = 0

        if len(eq_list) > 0:
            for e in eq_list:
                # print(e[0])
                self.A += e[0]
                self.B += e[1]
                self.C += e[2]

            self.A /= len(eq_list)
            self.B /= len(eq_list)
            self.C /= len(eq_list)

            # print("A: " + str(self.A) + ", B: " + str(self.B) + ", C: " + str(self.C))
            print("--------------------------------------------------------------------")
            print("-------------Restore the data to avoiding next calculation-----------")
            print("self.A = " + str(self.A))
            print("self.B = " + str(self.B))
            print("self.C = " + str(self.C))
            print("--------------------------------------------------------------------")


    def getTemperatureBySteinhartHartPara(self, A, B, C, r):
        temp = 1 / (A + B * math.log(r) + C * math.pow(math.log(r), 3) - 273.15)
        return temp

    def getResistorBySteinhartHartPara(self, A, B, C, c):
        r = sympy.Symbol('r')
        f = r**3 + B * r / C + A / C - 1 / self.getFahrenheitFromCelsius(c) / C
        r_rslt = sympy.solve(f)
        # In r_rslt, there may be real numbers and imaginary numbers, 
        #   and we just need the real number part
        for rr in r_rslt:
            try:
                # A imaginary number will throw an exception
                fv = float(rr)
                resistor = math.pow(math.e, rr)
                # print(resistor)
                return resistor
            except Exception as e:
                continue
        return 0


    def getCalibratedResistorList(self):
        self.calibration_y = []
        for temp in self.calibration_x:
            self.calibration_y.append(self.getResistorBySteinhartHartPara(self.A, self.B, self.C, temp))

        print(self.calibration_x)
        print(self.calibration_y)

    def calibration(self):
        print(len(self.raw_x))
        if (len(self.raw_x) == len(self.raw_y)) and len(self.raw_x) > 0:
            # Create raw index list
            raw_index_list = [x for x in range(0, len(self.raw_x))]
            # Get all combo result list by raw index list
            index_combo_list = list(combinations(raw_index_list, 3))
            
            eq_list = self.getSteinhartHartEquationList(index_combo_list, self.raw_x, self.raw_y)
            self.analyzeEquationList(eq_list)

            # This part calculate is for excel
            self.calibration_x = np.linspace(-30, 80, (int)((80 + 30) / 5) + 1)
            self.getCalibratedResistorList()
            self.savaCalibrationDataToExcelFile()
            # This part calculate is grid show
            self.calibration_x = np.linspace(-30, 80, 12)
            self.getCalibratedResistorList()
            plt.plot(self.raw_x, self.raw_y, 'k')
            plt.plot(self.calibration_x, self.calibration_y, 'b')
            plt.grid(True)
            plt.title("R-C table")
            plt.xlabel("Temperatue")
            plt.ylabel("Resistor")
            # plt.xticks([x for x in range(x_smooth[0], max(x_smooth) + 1) if x % 5 == 0])
            plt.show()

    def showPeople(self):
        x = [2017, 2018, 2019, 2020, 2021]
        y = [779, 530, 467, 204, 48]
        plt.plot(x, y, 'b')
        plt.grid(True)
        plt.show()



if __name__ == '__main__':
    if len(sys.argv) == 2:
        ntc = NTCTable()
        ntc.loadExcelFile(sys.argv[1])
        ntc.loadRawTable()
        ntc.calibration()
        # ntc.showPeople()